package com.urbane.generator.config;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.urbane.generator.properties.CodeGeneratorProperties;
import com.urbane.generator.service.GenDataSourceService;
import com.urbane.generator.service.GenTemplateService;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.ByteArrayOutputStream;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipOutputStream;

// 模板文件中可以获取的所有属性都在 AbstractTemplateEngine 中的 getObjectMap 方法方会的 objectMap 对象中
// 代码生成的入口为 FastAutoGenerator 中的 execute 方法
// 路径为 FastAutoGenerator/execute -> AutoGenerator/execute?templateEngine.init(config).batchOutput().open() 中的 batchOutput -> AbstractTemplateEngine/batchOutput?List<TableInfo> tableInfoList = config.getTableInfoList()
@Configuration
@RequiredArgsConstructor
public class GeneratorConfig {

    private final MybatisPlusProperties mybatisPlusProperties;

    private final CodeGeneratorProperties codeGeneratorProperties;

    private final GenDataSourceService genDatasourceService;

    private final GenTemplateService genTemplateService;

    // TODO 后续改进，通过前端动态配置数据库源，以及通过查询数据库获取模板文件
    // TODO 使用 hutool 工具包提供的 ZipUtil 压缩工具类将生成的代码压缩成 zip 文件

    // 请过 Post 请求从前端获取代码生成定制配置数据\
    @Bean
    public void codeGenerator() {
        // 使用 ByteArrayOutputStream 在内存中生成ZIP
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        CodeGeneratorProperties.DataSourceConfig dataSourceConfig = codeGeneratorProperties.getDataSourceConfig();

        // 使用 FastAutoGenerator 快速配置代码生成器
        FastAutoGenerator.create(
                        dataSourceConfig.getUrl(),
                        dataSourceConfig.getUsername(),
                        dataSourceConfig.getPassword()
                )
                // 全局配置提供了对代码生成器整体行为的设置，包括输出目录、作者信息、Kotlin 模式、Swagger 集成、时间类型策略等。
                // 参考 https://baomidou.com/reference/new-code-generator-configuration/#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE-globalconfig
                .globalConfig(builder -> {
                    CodeGeneratorProperties.GlobalConfig globalConfig = codeGeneratorProperties.getGlobalConfig();
                    builder.author(globalConfig.getAuthor())           // 作者
                            .outputDir(Paths.get(System.getProperty("user.dir")) + "/" + "project" + "/backend/src/main/java") // 输出目录
                            // 开启 Springdoc 注解
                            .enableSpringdoc()
                            .disableOpenDir();          // 生成后不打开目录
                })
                // 包配置用于定义生成代码的包结构，包括父包名、模块名、实体类包名、服务层包名等。
                // 参考 https://baomidou.com/reference/new-code-generator-configuration/#%E5%8C%85%E9%85%8D%E7%BD%AE-packageconfig
                .packageConfig(builder -> {
                    CodeGeneratorProperties.PackageConfig packageConfig = codeGeneratorProperties.getPackageConfig();
                    builder.parent(packageConfig.getParent())       // 父包名
                            .moduleName(packageConfig.getModuleName())       // 模块名
                            .entity(packageConfig.getEntity())     // 实体类包名
                            .controller(packageConfig.getController())   // Controller 包名
                            .service(packageConfig.getService())         // Service 包名
                            .serviceImpl(packageConfig.getServiceImpl())// Service 实现类包名
                            .mapper(packageConfig.getMapper())           // Mapper 接口包名
                            .xml(packageConfig.getXml())              // Mapper XML 包名
                            // 设置 mapper.xml 生成路径
                            .pathInfo(Collections.singletonMap(OutputFile.xml, Paths.get(System.getProperty("user.dir")) + "/project/backend/src/main/resources/mapper"));
                })
                // 策略配置是 MyBatis-Plus 代码生成器的核心部分，它允许开发者根据项目需求定制代码生成的规则，包括命名模式、表和字段的过滤、以及各个代码模块的生成策略。
                // 参考 https://baomidou.com/reference/new-code-generator-configuration/#%E7%AD%96%E7%95%A5%E9%85%8D%E7%BD%AE-strategyconfig
                .strategyConfig(builder -> {
                    CodeGeneratorProperties.StrategyConfig strategyConfig = codeGeneratorProperties.getStrategyConfig();
                    // 添加需要生成的表名
                    builder.addInclude(strategyConfig.getInclude()) // 设置需要生成的表名
                            .addTablePrefix(strategyConfig.getTablePrefix()); // 设置过滤表前缀

                    // Entity 策略配置
                    builder.entityBuilder()
                            .enableFileOverride() // 覆盖已生成文件，默认值: false
                            .enableLombok() // 开启 Lombok
                            .enableTableFieldAnnotation() // 开启生成实体时生成 @TableField 注解
                            .logicDeleteColumnName(strategyConfig.getEntity().getLogicDeleteColumnName()) // 逻辑删除字段名
                            .formatFileName(strategyConfig.getEntity().getFormatFileName()); // 格式化 Entity 文件名称
                    if (strategyConfig.getEntity().getSuperClass() != null) {
                        builder.entityBuilder().superClass(strategyConfig.getEntity().getSuperClass());
                    }

                    // Controller 策略配置
                    builder.controllerBuilder()
                            .enableFileOverride() // 覆盖已生成文件，默认值: false
                            .enableRestStyle() // 开启生成 @RestController 控制器
                            .formatFileName(strategyConfig.getController().getFormatFileName()); // 格式化 Controller 文件名称

                    // Service 策略配置
                    builder.serviceBuilder()
                            .enableFileOverride() // 覆盖已生成文件，默认值: false
                            .formatServiceFileName(strategyConfig.getService().getFormatServiceFileName()) // 格式化 service 接口文件名称
                            .formatServiceImplFileName(strategyConfig.getService().getFormatServiceImplFileName()); // 格式化 service 实现类文件名称

                    // Mapper 策略配置
                    builder.mapperBuilder()
                            .enableFileOverride() // 覆盖已生成文件，默认值: false
                            .enableBaseResultMap() // 启用 ResultMap 生成
                            .enableBaseColumnList() // 启用 columnList
                            .formatMapperFileName(strategyConfig.getMapper().getFormatMapperFileName()) // 格式化 Mapper 文件名称
                            .formatXmlFileName(strategyConfig.getMapper().getFormatXmlFileName()); // 格式化 Mapper XML 文件名称

                    builder.addInclude(strategyConfig.getInclude())  // 需生成的表名
                            .addTablePrefix(strategyConfig.getTablePrefix())       // 过滤表前缀

                            // 实体类策略配置
                            .entityBuilder()
                            // 覆盖已生成文件	默认值: false
                            .enableFileOverride()
                            // 启用 Lombok 默认值: false 默认只有 Getter,Setter,自3.5.10后增加 ToString
                            .enableLombok()
                            // 开启链式模型	默认值: false
                            .enableChainModel()
                            // 开启 Boolean 类型字段移除 is 前缀	默认值: false
                            .enableRemoveIsPrefix()
                            // 开启生成实体时生成字段注解	默认值: false
                            .enableTableFieldAnnotation()
                            // 全局主键类型
                            .idType(mybatisPlusProperties.getGlobalConfig().getDbConfig().getIdType())
                            // 乐观锁字段名(数据库字段)
                            .versionColumnName(strategyConfig.getEntity().getVersionColumnName())
                            // 逻辑删除字段名(数据库字段)
                            .logicDeleteColumnName(mybatisPlusProperties.getGlobalConfig().getDbConfig().getLogicDeleteField())
                            // 数据库表映射到实体的命名策略	默认下划线转驼峰命名: NamingStrategy.underline_to_camel
                            .naming(NamingStrategy.no_change)
                            // 数据库表字段映射到实体的命名策略	默认为 null，未指定按照 naming 执行
                            .columnNaming(NamingStrategy.underline_to_camel)
                            // 设置父类
                            .superClass(strategyConfig.getSuperEntityClass())
                            // 添加父类公共字段
                            .addSuperEntityColumns(strategyConfig.getEntity().getAddSuperEntityColumns())
                            // 添加忽略字段
                            .addIgnoreColumns("age")
                            // 添加表字段填充
                            .addTableFills(new Column(strategyConfig.getEntity().getAddTableFills().getFirst(), FieldFill.INSERT))
                            .addTableFills(new Column(strategyConfig.getEntity().getAddTableFills().getLast(), FieldFill.INSERT_UPDATE))
                            // 格式化文件名称
                            .formatFileName("%s")

                            // Controller 策略配置
                            .controllerBuilder()
                            // 覆盖已生成文件	默认值: false
                            .enableFileOverride()
                            // 开启生成@RestController 控制器	默认值: false
                            .enableRestStyle()
                            .formatFileName("%sController")
                            .template("/templates/java/controller.java")

                            // Service 策略配置
                            .serviceBuilder()
                            .enableFileOverride() // 覆盖已生成文件
                            .superServiceClass(IService.class)
                            .formatServiceFileName("%sService") // 格式化 service 接口文件名称，将格式设定为 entity名 + Service
                            .serviceTemplate("/templates/java/service.java") // 设置自定义 Service 模板
                            .superServiceImplClass(ServiceImpl.class)
                            .formatServiceImplFileName("%sServiceImp")
                            .serviceImplTemplate("/templates/java/serviceImpl.java")

                            // Mapper 策略配置
                            .mapperBuilder()
                            .enableFileOverride()
                            .superClass(BaseMapper.class)
                            // 开启 @Mapper 注解	默认值: false
                            .mapperAnnotation(Mapper.class)
                            // 格式化 Mapper 文件名称
                            .formatMapperFileName("%sMapper")
                            .mapperTemplate("/templates/java/mapper.java")
                            // 设置 Mapper XML 策略配置
                            .enableBaseResultMap()
                            // 启用 BaseColumnList	默认值: false
                            .enableBaseColumnList()
                            // 格式化 XML 实现类文件名称
                            .formatXmlFileName("%sMapper")
                            .mapperXmlTemplate("/templates/xml/mapper.xml");
                })
                // 通过 injectionConfig 方法，你可以灵活地自定义模板路径和输出位置，从而生成与后端代码对应的前端 Vue 和 TypeScript 数据表格代码
                // 注入配置允许开发者自定义代码生成器的行为，包括在输出文件之前执行的逻辑、自定义配置 Map 对象、自定义配置模板文件等。
                // 参考 https://baomidou.com/reference/new-code-generator-configuration/#%E6%B3%A8%E5%85%A5%E9%85%8D%E7%BD%AE-injectionconfig
                .injectionConfig(builder -> {
                    Map<String, Object> customMap = new HashMap<>();
                    customMap.put("baseEntityPath", "customValue");
                    builder
                            .customMap(customMap) //注入自定义属性，参考 https://baomidou.com/reference/new-code-generator-configuration/#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E6%9D%BF%E6%94%AF%E6%8C%81-dtovo-%E7%AD%89-%E9%85%8D%E7%BD%AE
                            // BaseEntity 文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("model.entity.base") // 具有默认的 packageConfig 配置的（parent + moduleName）前缀包名
                                            .fileName("BaseEntity.java") // 文件名称会默认添加表名 EntityName 作为前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义前缀
                                            .formatNameFunction(tableInfo -> "")
                                            .templatePath("templates/java/base-entity.java.ftl") //指定生成模板路径
                                            .build()
                            )
                            // DTO 文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("model.dto") // 包名,自3.5.10开始,可通过在package里面获取自定义包全路径,低版本下无法获取,示例:package.entityDTO
                                            .fileName("DTO.java") // 文件后缀公共名称，会默认添加表名 EntityName 作为文件名前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义文件名前缀
                                            .templatePath("templates/java/dto.java.ftl") //指定生成模板路径
                                            .formatNameFunction(tableInfo -> tableInfo.getEntityName().toLowerCase())
                                            .build()
                            )
                            // VO 文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("model.vo")
                                            .formatNameFunction(tableInfo -> tableInfo.getEntityName().toLowerCase())
                                            .fileName("VO.java") // 文件名称
                                            .templatePath("templates/java/vo.java.ftl") //指定生成模板路径
                                            .build()
                            )
                            // application 文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .formatNameFunction(tableInfo -> "")
                                            .fileName("application.yaml") // 文件后缀公共名称，会默认添加表名 EntityName 作为文件名前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义文件名前缀
                                            .filePath(System.getProperty("user.dir") + "/project/backend/src/main/resources")
                                            .templatePath("templates/java/application.yaml.ftl") //指定生成模板路径
                                            .build()
                            )
                            // API 文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("api")
                                            .fileName("Api.ts") // 文件后缀公共名称，会默认添加表名 EntityName 作为文件名前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义文件名前缀
                                            .filePath(System.getProperty("user.dir") + "/project/frontend/src")
                                            .templatePath("templates/ts/api.ts.ftl") //指定生成模板路径
                                            .build()
                            )
                            // TypeScript 类型定义文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("types")
                                            .fileName("Type.ts") // 文件后缀公共名称，会默认添加表名 EntityName 作为文件名前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义文件名前缀
                                            .filePath(System.getProperty("user.dir") + "/project/frontend/src")
                                            .templatePath("templates/ts/types.ts.ftl") //指定生成模板路径
                                            .build()
                            )
                            // Vue 组件文件
                            .customFile(
                                    new CustomFile.Builder()
                                            .enableFileOverride()
                                            .packageName("views")
                                            .fileName("Table.vue") // 文件后缀公共名称，会默认添加表名 EntityName 作为文件名前缀，如果不需要，需要自行处理，可以通过 formatNameFunction 自定义文件名前缀
                                            .filePath(System.getProperty("user.dir") + "/project/frontend/src")
                                            .templatePath("templates/vue/table.vue.ftl") //指定生成模板路径
                                            .build()
                            )
                            // TODO 在生成文件之前执行特定的逻辑，例如： 代码预览
                            .beforeOutputFile((tableInfo, objectMap) -> {
                                System.out.println("准备生成文件: " + tableInfo.getEntityName());
                                // 可以在这里添加自定义逻辑，如修改 objectMap 中的配置
                                if (objectMap.get("package") != null && objectMap.get("package") instanceof Map packageMap) {
                                    String parent = (String) packageMap.get("Parent");
                                    System.out.println("父包名为：" + parent);
                                }
                            });
                })
                // 模板引擎配置
                .templateEngine(new FreemarkerTemplateEngine())// 使用 Freemarker 引擎模板，默认的是 Velocity 引擎模板
                .execute(); // 执行生成
    }
}